package com.dycx.api.config;

import com.alibaba.fastjson.JSON;
import com.dycx.common.RestResponse;
import com.dycx.common.annotation.WriterLog;
import com.dycx.system.dto.LogCreateDTO;
import com.dycx.system.service.LogService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.List;

/**
 * 统一日志处理切面
 */
@Aspect
@Component
@Order(10)
public class AdminLogAspect {

    @Resource
    private LogService logService;

    long startTime = 0L;

    //定义切点表达式,指定通知功能被应用的范围
    @Pointcut("execution(public * com.dycx.api.controller.manage.*.*.*(..))")
    public void logPointcut1() {

    }

    @Pointcut("execution(public * com.dycx.api.controller.app.*.*.*(..))")
    public void logPointcut2() {

    }

    @Pointcut("logPointcut1() || logPointcut2()")
    public void logPointcut() {
    }

    @Before("logPointcut()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        startTime = System.currentTimeMillis();
    }

    /**
     * value切入点位置
     * returning 自定义的变量，标识目标方法的返回值,自定义变量名必须和通知方法的形参一样
     * 特点：在目标方法之后执行的,能够获取到目标方法的返回值，可以根据这个返回值做不同的处理
     */
    @AfterReturning(value = "logPointcut()", returning = "result")
    public void doAfterReturning(JoinPoint joinPoint, Object result) throws Throwable {
        //获取当前请求对象
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();

        // 有@WriterLog注释的写入日志
        if (method.isAnnotationPresent(WriterLog.class)) {
            RestResponse restResponse = JSON.parseObject(JSON.toJSONString(result), RestResponse.class);

            long endTime = System.currentTimeMillis();

            //post数据处理
            String body = null;
            if (!method.getAnnotation(WriterLog.class).hideRequest()) {
                if (!JSON.toJSONString(joinPoint.getArgs()).equals("[]")) {
                    body = JSON.toJSONString(joinPoint.getArgs()[0]);
                }
            }

            //是否记录返回结果
            if (method.getAnnotation(WriterLog.class).hideResponse()) {
                restResponse.data = "";
            }

            int bizType = method.getAnnotation(WriterLog.class).bizType().getCode();
            String bizValue = generateKeyBySpEL(method.getAnnotation(WriterLog.class).bizValue(), joinPoint);

            String logTitle = method.getAnnotation(WriterLog.class).logTitle();
            String logRemark = generateKeyBySpEL(method.getAnnotation(WriterLog.class).logRemark(), joinPoint);

            LogCreateDTO dto = new LogCreateDTO();
            dto.setRequest(request);
            dto.setPost(body);
            dto.setResponse(restResponse);
            dto.setSpeedTime((int) (endTime - startTime));
            dto.setBizType(bizType);
            dto.setBizValue(bizValue);
            dto.setLogTitle(logTitle);
            dto.setLogRemark(logRemark);
            logService.addLog(dto);
        }
    }

    public String generateKeyBySpEL(String spELString, JoinPoint joinPoint) {
        if (spELString.isEmpty()) {
            return "";
        }
        SpelExpressionParser parser = new SpelExpressionParser();
        DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();

        // 通过joinPoint获取被注解方法
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        // 使用spring的DefaultParameterNameDiscoverer获取方法形参名数组
        String[] paramNames = nameDiscoverer.getParameterNames(method);
        // 解析过后的Spring表达式对象
        Expression expression = parser.parseExpression(spELString);
        // spring的表达式上下文对象
        EvaluationContext context = new StandardEvaluationContext();
        // 通过joinPoint获取被注解方法的形参
        Object[] args = joinPoint.getArgs();
        // 给上下文赋值
        for (int i = 0; i < args.length; i++) {
            context.setVariable(paramNames[i], args[i]);
        }
        // 表达式从上下文中计算出实际参数值
        /*如:
            @annotation(key="#student.name")
             method(Student student)
             那么就可以解析出方法形参的某属性值，return “xiaoming”;
          */
        Object obj = expression.getValue(context);
        String str = JSON.toJSONString(obj);
        if ("[null]".equals(str)) {
            return "";
        }
        List<String> arr = JSON.parseArray(str, String.class);

        return arr.get(0);
    }
}
